<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Z6MUs" id="Z6MUs"><span data-lake-id="ua10f7f20" id="ua10f7f20">典型回答</span></h1>
  <p data-lake-id="u738951d1" id="u738951d1"><br></p>
  <p data-lake-id="uaec5446d" id="uaec5446d"><span data-lake-id="u8aa0f5d4" id="u8aa0f5d4">数据库的预编译（Prepared Statement）是一种数据库查询优化技术，在预编译中，可以先先提交带占位符的 SQL ，MySQL 先将其编译好，然后用户再拿着SQL中定义的占位符对应的参数让 MySQL 去执行。</span></p>
  <p data-lake-id="u4783be45" id="u4783be45"><span data-lake-id="udc8d500f" id="udc8d500f">​</span><br></p>
  <p data-lake-id="ud06b3cf5" id="ud06b3cf5"><span data-lake-id="ub5fc8f24" id="ub5fc8f24">当一个SQL被预编译之后，预编译的SQL模板中的占位符，比如问号（?），是作为参数的位置标记存在的，而不是作为SQL语句的一部分。无论用户输入什么样的参数，这些参数都会被视为数据，而不会被解释为SQL语句的一部分。</span></p>
  <p data-lake-id="u17f3da2f" id="u17f3da2f"><span data-lake-id="u80431a73" id="u80431a73">​</span><br></p>
  <p data-lake-id="uae13b4d2" id="uae13b4d2"><span data-lake-id="ua80a2975" id="ua80a2975">也就是说，用户输入的恶意代码会被当作普通的数据处理，而不会被解释为SQL语句的一部分。如果恶意注入的内容打破了SQL语句的语法结构，数据库可能会在执行阶段产生语法错误，导致查询无法成功执行。这实际上是数据库系统试图解析预编译模板时的结果，因为预编译模板本身的语法是固定的。</span></p>
  <p data-lake-id="uf77f1af4" id="uf77f1af4"><span data-lake-id="u07ac19c2" id="u07ac19c2">​</span><br></p>
  <p data-lake-id="ue4c54340" id="ue4c54340"><span data-lake-id="ucd1eefc2" id="ucd1eefc2">举一个简单的例子，加入我们有一个原始SQL：</span></p>
  <p data-lake-id="ue06f5be2" id="ue06f5be2"><span data-lake-id="u618a70a4" id="u618a70a4">​</span><br></p>
  <pre lang="java"><code>
// 原始查询模板
$sql = "SELECT * FROM users WHERE username = ?";
</code></pre>
  <p data-lake-id="ud88412e7" id="ud88412e7"><span data-lake-id="u9d892686" id="u9d892686">​</span><br></p>
  <p data-lake-id="ua31da03f" id="ua31da03f"><span data-lake-id="u4f96d3fb" id="u4f96d3fb">当用户输入恶意代码尝试进行SQL注入：</span></p>
  <p data-lake-id="u13fb4580" id="u13fb4580"><span data-lake-id="u1bc4bfc8" id="u1bc4bfc8">​</span><br></p>
  <pre lang="java"><code>
$userInput = "hollis'; DROP TABLE users; --";
</code></pre>
  <p data-lake-id="u54a00109" id="u54a00109"><br></p>
  <p data-lake-id="ude079368" id="ude079368"><span data-lake-id="u8ea8b519" id="u8ea8b519">用户期望最终这个SQL变成：</span></p>
  <p data-lake-id="u9baf04dd" id="u9baf04dd"><span data-lake-id="u46ecc880" id="u46ecc880">​</span><br></p>
  <pre lang="java"><code>
SELECT * FROM users WHERE username = 'hollis'; DROP TABLE users; --'
</code></pre>
  <p data-lake-id="u7610ef74" id="u7610ef74"><br></p>
  <p data-lake-id="udbd3baf0" id="udbd3baf0"><span data-lake-id="ufc88f8e1" id="ufc88f8e1">会导致数据表被删除。那么，如果用了预编译之后，就不会出现这种问题了。</span></p>
  <p data-lake-id="u5fa7f108" id="u5fa7f108"><span data-lake-id="u10170d8a" id="u10170d8a">​</span><br></p>
  <p data-lake-id="u3cba0481" id="u3cba0481"><span data-lake-id="u753d1384" id="u753d1384">预编译过程如下：</span></p>
  <p data-lake-id="ud16c9c3b" id="ud16c9c3b"><span data-lake-id="uc1623340" id="uc1623340">​</span><br></p>
  <pre lang="java"><code>
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ?';

EXECUTE stmt USING @userInput;

DEALLOCATE PREPARE stmt;
</code></pre>
  <p data-lake-id="u848c9ff8" id="u848c9ff8"><span data-lake-id="u05170010" id="u05170010">语法参考： </span><a href="https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html" target="_blank" data-lake-id="u9a8a087d" id="u9a8a087d"><span data-lake-id="ub0a65731" id="ub0a65731">https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html</span></a><span data-lake-id="udb935cdd" id="udb935cdd"> </span></p>
  <p data-lake-id="u5cdac90a" id="u5cdac90a"><span data-lake-id="ub51c692a" id="ub51c692a">​</span><br></p>
  <p data-lake-id="u1dd69c7e" id="u1dd69c7e"><span data-lake-id="uf0f34b33" id="uf0f34b33">在这种情况下，用户输入包含对SQL注入的尝试（hollis'; DROP TABLE users; --）。然而，由于预处理语句和参数绑定，这个输入被视为字符串而不是可执行的SQL代码。实际执行的查询是：</span></p>
  <p data-lake-id="u8b7660ca" id="u8b7660ca"><span data-lake-id="u82cd6d34" id="u82cd6d34">​</span><br></p>
  <pre lang="java"><code>
SELECT * FROM users WHERE username ='john_doe\'; DROP TABLE users; --'
</code></pre>
  <p data-lake-id="uf63ec55e" id="uf63ec55e"><br></p>
  <p data-lake-id="ufd7fd7a1" id="ufd7fd7a1"><span data-lake-id="uad076af1" id="uad076af1">那么也就是说，用户输入的整个部分，都作为字符串的一部分了，会去数据库中查询username为</span><code data-lake-id="uac726374" id="uac726374"><span data-lake-id="u0d5223ad" id="u0d5223ad">john_doe\'; DROPTABLE users; --</span></code><span data-lake-id="ud284cf7a" id="ud284cf7a">的用户，这有效的避免了SQL注入。</span></p>
  <p data-lake-id="u85cdcdb1" id="u85cdcdb1"><span data-lake-id="u31252928" id="u31252928">​</span><br></p>
  <p data-lake-id="u9d2f9258" id="u9d2f9258"><span data-lake-id="u3e7de97e" id="u3e7de97e">​</span><br></p>
  <h1 data-lake-id="lHrUA" id="lHrUA"><span data-lake-id="ue2718074" id="ue2718074">扩展知识</span></h1>
  <p data-lake-id="u2b29e653" id="u2b29e653"><br></p>
  <h2 data-lake-id="DoGNQ" id="DoGNQ"><span data-lake-id="u3cdc4d1a" id="u3cdc4d1a">Java中使用预编译</span></h2>
  <p data-lake-id="ua376f3a2" id="ua376f3a2"><br></p>
  <p data-lake-id="u9ec0194a" id="u9ec0194a"><span data-lake-id="u9a6b3965" id="u9a6b3965">在Java中，PreparedStatement是java.sql包中的一个接口，用于执行带有预编译参数的SQL语句。它是Statement接口的子接口，提供了更强大、更安全的SQL执行机制。</span></p>
  <p data-lake-id="uaa88dbf4" id="uaa88dbf4"><span data-lake-id="ua760e2cd" id="ua760e2cd">​</span><br></p>
  <p data-lake-id="u59238927" id="u59238927"><span data-lake-id="u022a49b2" id="u022a49b2">以下是使用PreparedStatement的简单用法，可以有效的避免SQL注入：</span></p>
  <p data-lake-id="u1078e60a" id="u1078e60a"><span data-lake-id="ud4e25d5a" id="ud4e25d5a">​</span><br></p>
  <pre lang="java"><code>
// 用户输入（可能是恶意输入）
String userInput = "hollis'; DROP TABLE users; --";

// 原始查询模板
String sql = "SELECT * FROM users WHERE username = ?";

// 使用PreparedStatement进行预编译
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
    // 绑定参数
    preparedStatement.setString(1, userInput);

    // 执行查询
    try (ResultSet resultSet = preparedStatement.executeQuery()) {
        // 处理结果（简化起见，只是输出）
        while (resultSet.next()) {
            System.out.println(resultSet.getString("username"));
        }
    }
} catch (SQLException e) {
    e.printStackTrace();
}
</code></pre>
 </body>
</html>